//
//  LanBSio.m
//  LanBKit_Example
//
//  Created by v on 2020/10/31.
//  Copyright © 2020 v. All rights reserved.
//

#import "LanBSio.h"
#include <SocketIO-Client-CPP/sio_client.h>
#import <Base64.h>

@interface LanbSioBlocker : NSObject
@property (nonatomic, copy) callbackBlock block;
@end
@implementation LanbSioBlocker
@end

@interface LanBSio()
{
    sio::client *_io;
}
@property (nonatomic, strong) NSString     *urlStr;
@property (nonatomic, strong) NSNumber     *reconnectAttempts;
@property (nonatomic, strong) NSNumber     *reconnectWait;
@property (nonatomic, strong) NSDictionary *config;
@property (nonatomic, copy  ) statusBlock  block;
@property (nonatomic, strong) NSMutableArray<LanbSioBlocker*> *aryCallbackBlocker;
@end

@implementation LanBSio

- (instancetype)initWithUrlString:(NSString *)url reconnectAttempts:(NSNumber *)attempts reconnectWait:(NSNumber *)wait config:(NSDictionary *)config
{
    if (self = [super init]) {
        self.urlStr = url;
        self.reconnectAttempts = attempts;
        self.reconnectWait = wait;
        self.config = config;
        [self _setupSocketIO];
    }
    return self;
}

- (void)connect
{
    std::map<std::string,std::string> params;
    for (NSString *key in self.config) {
        NSObject *obj = self.config[key];
        if (![key isKindOfClass:NSString.class])
            continue;
        if ([obj isKindOfClass:NSString.class]) {
            params[key.UTF8String] = ((NSString*)obj).UTF8String;
        }
        if ([obj isKindOfClass:NSNumber.class]) {
            params[key.UTF8String] = ((NSNumber*)obj).stringValue.UTF8String;
        }
    }
    std::string url = self.urlStr.UTF8String;
    _io->connect(url, params);
}

- (void)disConnect
{
    if (_io) {
        _io->close();
    }
}

- (void)removeAllHandlers
{
    if (_io) {
        _io->socket()->off_error();
        _io->socket()->off_all();
        _io->clear_con_listeners();
        _io->clear_socket_listeners();
    }
    [self.aryCallbackBlocker removeAllObjects];
}

- (void)handleStatus:(void (^)(LanBSioStatus))statusBlock
{
    self.block = statusBlock;
}

- (void)emitWithAck:(NSString*)event item:(NSArray*)items callback:(void (^)(NSArray *))callback
{
    if (!_io)
        return;
    
    std::string name = event.UTF8String;
    sio::message::list msglist = [self _transferToCppList:items];
    
    LanbSioBlocker *blocker;
    if (callback) {
        blocker = [LanbSioBlocker new];
        blocker.block = callback;
        [self.aryCallbackBlocker addObject:blocker];
    }
    
    LanBSio &mySelf = *self;
    LanbSioBlocker &b = *blocker;
    _io->socket()->emit(name, msglist, [&](sio::message::list ack){
        LanBSio *self = &mySelf;
        NSArray *res = [self _transferToObjCList:ack];
        LanbSioBlocker *blocker = &b;
        callbackBlock block = blocker.block;
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block)
                block(res);
        });
    });
}

- (void)on:(NSString*)event callback:(callbackBlock)callback
{
    if (!_io)
        return;
    
    std::string name = event.UTF8String;
    
    LanbSioBlocker *blocker;
    if (callback) {
        blocker = [LanbSioBlocker new];
        blocker.block = callback;
        [self.aryCallbackBlocker addObject:blocker];
    }
    
    LanBSio &mySelf = *self;
    LanbSioBlocker &b = *blocker;
    _io->socket()->on(name, sio::socket::event_listener_aux([&](sio::string const& name, sio::message::ptr const& data, bool isAck, sio::message::list &ack_resp){
        
        LanBSio *self = &mySelf;
        NSArray *res = nil;
        NSObject *r = [self _transferToObj:data];
        if ([r isKindOfClass:NSArray.class]) {
            res = (NSArray*)r;
        }else{
            r ? res = @[r] : NULL;
        }
        LanbSioBlocker *blocker = &b;
        callbackBlock block = blocker.block;
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block)
                block(res);
        });
        
        /*
        if(isAck){ // 回传
            sio::message::ptr temp = nullptr;
            ack_resp.push(temp);
        }
        */
    }));
                      
}

#pragma mark - private

- (void)_setupSocketIO
{
    [self _destroySocketIO];
    
    // 创建 client
    _io = new sio::client();
    sio::socket::ptr socket = _io->socket();
    
    int attempts = self.reconnectAttempts.intValue;
    int delay_max = self.reconnectWait.intValue > 5 ? self.reconnectWait.intValue : 5;
    
    // 设置重连参数
    _io->set_reconnect_attempts(attempts);        // -1 无限重连, 0 无重连, >0表示重连次数
    _io->set_reconnect_delay(delay_max*1000);     // 最小等待重连时间
    _io->set_reconnect_delay_max(delay_max*1000); // 最大等待重连时间
    
    // 注册监听
    [self _setListener];
    
}

- (void)_destroySocketIO
{
    if (_io) {
        [self disConnect];
        [self removeAllHandlers];
        _io = NULL;
    }
}

- (void)_setListener
{
    // 连接成功 首先进入该回调
    _io->set_open_listener(std::bind(&On_set_open_listener, (__bridge CFTypeRef)self));
    // 连接成功之后，断开连接 进入该回调
    _io->set_close_listener(std::bind(&OnC_set_close_listener, (__bridge CFTypeRef)self,std::placeholders::_1));
    _io->set_reconnecting_listener(std::bind(&On_set_reconnecting_listener, (__bridge CFTypeRef)self));
    
    LanBSio &mySelf = *self;
    // 首次连接, 失败回调
    _io->set_fail_listener([&](void){
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"set_fail_listener");
            LanBSio *self = &mySelf;
            if (self.block) {
                self.block(LanBSioStatus_Fail);
            }
        });
    });
    // 开启重连机制后, 连接失败 进入该回调
    _io->set_reconnect_listener([&](unsigned test1, unsigned test2){
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"set_reconnect_listener: %d %d" , test1 , test2);
            LanBSio *self = &mySelf;
            if (self.block) {
                self.block(LanBSioStatus_Reconnect);
            }
        });
    });
    
    // 连接socket成功
    _io->set_socket_open_listener([&](std::string const& nsp){
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"set_socket_open_listener");
            LanBSio *self = &mySelf;
            if (self.block) {
                self.block(LanBSioStatus_Open);
            }
        });
    });
    
    // 断开socket之后
    _io->set_socket_close_listener([&](std::string const& nsp){
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"set_socket_close_listener");
            LanBSio *self = &mySelf;
            if (self.block) {
                self.block(LanBSioStatus_Close);
            }
        });
    });
    
}

void On_set_open_listener(CFTypeRef cfTR)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"On_set_open_listener");
    });
}

void OnC_set_close_listener(CFTypeRef cfTR,sio::client::close_reason const& reason)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"OnC_set_close_listener");
    });
}

void On_set_reconnecting_listener(CFTypeRef cfTR)
{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"On_set_reconnecting_listener");
//        NSLog(@"%@", (__bridge LanBSio*)cfTR);
    });
    /*
     CFTypeRef ctrl = (__bridge CFTypeRef)self;
     LanBSio *sio = (__bridge LanBSio*)ctrl;
     
     void *cPointer = (__bridge_retained void*)self;
     const void *cPointer;
     cPointer = CFBridgingRetain(self);
     LanBSio *sio = (__bridge_transfer LanBSio *)cPointer;
     */
}

- (sio::message::list)_transferToCppList:(NSArray*)ary
{
    if (ary.count==0)
        return NULL;
    sio::message::list msglist = nullptr;
    for (int i = 0; i<ary.count; i++) {
        NSObject *obj = ary[i];
        sio::message::ptr ptr = [self _transferToCppMessage:obj];
        msglist.push(ptr);
    }
    return msglist;
}

- (NSArray*)_transferToObjCList:(sio::message::list)list
{
    if (list.size()==0)
        return nil;
    NSMutableArray *ary = [NSMutableArray new];
    for (int i=0; i<list.size(); i++) {
        sio::message::ptr msg = list[i];
        NSObject *o = [self _transferToObj:msg];
        if (o) {
            [ary addObject:o];
        }
    }
    return ary;
}

/**
 将 oc对象 转换为 c++ sio::message::ptr对象
 */
- (sio::message::ptr)_transferToCppMessage:(NSObject*)obj
{
    sio::message::ptr ptr = nullptr;
    
    // 处理 NSNumber类型
    if ([obj isKindOfClass:NSNumber.class]) {
        NSNumber *objNum = (NSNumber*)obj;
        if (strcmp([objNum objCType], @encode(float)) == 0){
            // flag_double
            ptr = sio::double_message::create(objNum.doubleValue);
        }else if (strcmp([objNum objCType], @encode(double)) == 0){
            // flag_double
            ptr = sio::double_message::create(objNum.doubleValue);
        }else if (strcmp([objNum objCType], @encode(BOOL)) == 0){
            // flag_boolean
            ptr = sio::bool_message::create(objNum.boolValue);
        }else{
            // flag_integer
            ptr = sio::int_message::create(objNum.longLongValue);
        }
    }
    
    // 处理 NSString类型
    if ([obj isKindOfClass:NSString.class]) {
        // flag_string
        NSString *objStr = (NSString*)obj;
        ptr = sio::string_message::create(objStr.UTF8String);
    }
    
    // 处理 NSData类型, uiimage的data会丢失
    if ([obj isKindOfClass:NSData.class]) {
        // flag_binary
        NSData *objData = (NSData*)obj;
        char *c= (char*)[objData bytes];
        std::shared_ptr<const std::string> p (new sio::string(c, objData.length));
        ptr = sio::binary_message::create(p);
    }
    
    // 处理 NSArray类型
    if ([obj isKindOfClass:NSArray.class]) {
        // flag_array
        NSArray *objAry = (NSArray*)obj;
        sio::array_message::ptr listPtr = sio::array_message::create();
        for (int i = 0; i<objAry.count; i++) {
            NSObject *obj = objAry[i];
            sio::message::ptr ptr = [self _transferToCppMessage:obj];
            listPtr->get_vector().push_back(ptr);
        }
        if (listPtr->get_vector().size() > 0) {
            ptr = listPtr;
        }
    }
    
    // 处理 NSDictionary类型
    if ([obj isKindOfClass:NSDictionary.class]) {
        // flag_object
        NSDictionary *objDic = (NSDictionary*)obj;
        sio::object_message::ptr mapPtr = sio::object_message::create();
        for (NSString *key in objDic) {
            NSObject *obj = objDic[key];
            if (![key isKindOfClass:NSString.class])
                continue;
            mapPtr->get_map()[key.UTF8String] = [self _transferToCppMessage:obj];
        }
        if (mapPtr->get_map().size() > 0) {
            ptr = mapPtr;
        }
    }
    
    return ptr;
}

/**
 将 c++ sio::message::ptr对象 转换为 oc 对象
 */
- (NSObject*)_transferToObj:(sio::message::ptr)ptr
{
    NSObject *obj = nil;
    sio::message::flag flag = ptr->get_flag();
    
    // 转为整型 NSNumber
    if (sio::message::flag_integer == flag) {
        obj = [NSNumber numberWithLongLong:ptr->get_int()];
    }
    // 转为double NSNumber
    if (sio::message::flag_double == flag) {
        obj = [NSNumber numberWithDouble:ptr->get_double()];
    }
    // 转为NSString
    if (sio::message::flag_string == flag) {
        obj = [NSString stringWithUTF8String:ptr->get_string().data()];
    }
    // 转为bool NSNumber
    if (sio::message::flag_boolean == flag) {
        obj = [NSNumber numberWithBool:ptr->get_bool()];
    }
    // 转为 NSData
    if (sio::message::flag_binary == flag) {
        sio::string s = *(ptr->get_binary());
        char *c = (char*)s.data();
        obj = [NSData dataWithBytes:c length:strlen(c)];
    }
    // 转为 NSArray
    if (sio::message::flag_array == flag) {
        NSMutableArray *ary = [NSMutableArray new];
        for (int i=0; i<ptr->get_vector().size(); i++) {
            sio::message::ptr msg = ptr->get_vector()[i];
            NSObject *o = [self _transferToObj:msg];
            if (o) {
                [ary addObject:o];
            }
        }
        if (ary.count>0) {
            obj = ary;
        }
    }
    // 转为 NSDictionary
    if (sio::message::flag_object == flag) {
        NSMutableDictionary *dic = [NSMutableDictionary new];
        for(auto const &au : ptr->get_map()) {
            NSString *key = [NSString stringWithUTF8String:au.first.data()];
            NSObject *o = [self _transferToObj:au.second];
            if (o) {
                dic[key] = o;
            }
        }
        if (dic.count>0) {
            obj = dic;
        }
    }
    
    return obj;
}

#pragma mark - get set
- (NSMutableArray<LanbSioBlocker *> *)aryCallbackBlocker
{
    if (!_aryCallbackBlocker) {
        _aryCallbackBlocker = [NSMutableArray new];
    }
    return _aryCallbackBlocker;
}

- (void)dealloc
{
    [self _destroySocketIO];
    NSLog(@"-----*** dealloc -- %@" , self);
}

@end
